﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Xml;
using System.IO;
using System.Text;
using System.Reflection;
using System.Threading;

namespace Daybreak.Core.Network
{
    public delegate void Error(string message);

    class StateObject
    {
        public const int buffer_size = 1048576;
        public byte[] buffer = new byte[buffer_size];
        public Socket socket = null;
    }

    public class NetBase
    {
        internal double ping = 0;
        internal Dictionary<int, NetPacket> userPackets = new Dictionary<int, NetPacket>();
        internal bool configLoaded = false;

        private string assembly;
        private Timer pingTimer;

        protected XmlDocument configXml;

        public event Error OnError;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="assemblyFile">Path to assembly where are located user's packets</param>
        public NetBase(string assemblyFile)
        {
            assembly = assemblyFile;

            AddSystemPackets();

            OnError += new Error(NetError);
        }

        public virtual void Send(NetPacket packet) { }

        public void NetError(string message)
        {
            OnError(message);
        }

        /// <summary>
        /// Load all users's packets from config file
        /// </summary>
        internal void LoadPacketsFromConfig(string config)
        {
            XmlDocument configXml = new XmlDocument();
            configXml.LoadXml(config);

            Assembly assemblyPacket = Assembly.LoadFile(assembly);

            foreach (XmlNode item in configXml.DocumentElement.ChildNodes)
            {
                foreach (Type type in assemblyPacket.GetTypes())
                {
                    if (type.FullName.Equals(item.Attributes["type"].Value))
                    {
                        NetPacket pak = (NetPacket)Activator.CreateInstance(type);
                        pak.PacketID = int.Parse(item.Attributes["id"].Value);
                        userPackets.Add(int.Parse(item.Attributes["id"].Value), pak);
                    }
                }
            }
        }

        private void AddSystemPackets()
        {
            SystemGetConfigFilePacket pack0 = new SystemGetConfigFilePacket();
            pack0.PacketID = 0;
            userPackets.Add(pack0.PacketID, pack0);

            SystemUdpConnectPacket pack1 = new SystemUdpConnectPacket();
            pack1.PacketID = 1;
            userPackets.Add(pack1.PacketID, pack1);

            SystemUdpClosePacket pack2 = new SystemUdpClosePacket();
            pack2.PacketID = 2;
            userPackets.Add(pack2.PacketID, pack2);

            SystemPing pack3 = new SystemPing();
            pack3.PacketID = 3;
            userPackets.Add(pack3.PacketID, pack3);
        }

        protected virtual void PingTimer(Object stateInfo)
        {
        }

        protected void StartPing()
        {
            pingTimer = new Timer(new TimerCallback(PingTimer), null, 10, 1000);
        }

        protected void StopPing()
        {
            pingTimer.Dispose();
        }

        /// <summary>
        /// Create a configuration file based on a custom assembly
        /// </summary>
        protected void CreatePacketConfig()
        {
            try
            {
                configXml = new XmlDocument();

                XmlDeclaration xmldecl = configXml.CreateXmlDeclaration("1.0", "UTF-8", null);
                configXml.AppendChild(xmldecl);

                XmlDocumentFragment fch = configXml.CreateDocumentFragment();
                fch.InnerXml = "<packets></packets>";
                configXml.AppendChild(fch);

                Assembly assemblyPacket = Assembly.LoadFile(assembly);

                int id = userPackets.Count;

                foreach (Type type in assemblyPacket.GetTypes())
                {
                    if (type.IsSubclassOf(typeof(NetPacket)))
                    {
                        NetPacket pak = (NetPacket)Activator.CreateInstance(type);
                        pak.PacketID = id;
                        userPackets.Add(id, pak);

                        StringBuilder sb = new StringBuilder();
                        sb.AppendFormat("<packet id=\"{0}\" type=\"{1}\" />", id, type.FullName);

                        XmlDocumentFragment frg = configXml.CreateDocumentFragment();
                        frg.InnerXml = sb.ToString();

                        configXml.DocumentElement.AppendChild(frg);

                        id++;
                    }
                }
                configXml.Normalize();
            }
            catch (Exception ex)
            {
                throw;
            }
        }

        /// <summary>
        /// Search packet id
        /// </summary>
        /// <param name="packet">User packet</param>
        /// <returns>return -1 if nothing find otherwise return packet ID</returns>
        protected int GetPacketID(NetPacket packet)
        {
            foreach (NetPacket pack in userPackets.Values)
            {
                if (packet.GetType().FullName.Equals(pack.GetType().FullName))
                {
                    return pack.PacketID;
                }
            }
            return -1;
        }

        protected NetPacket GetPacket(int id)
        {
            NetPacket packet;
            userPackets.TryGetValue(id, out packet);
            return packet;
        }

        public double Ping
        {
            get
            {
                return ping;
            }
        }

        internal double TotalMilliseconds
        {
            get
            {
                return new TimeSpan(DateTime.Now.Hour, DateTime.Now.Minute, DateTime.Now.Second).TotalMilliseconds;
            }
        }

        internal string Config
        {
            get
            {
                return configXml.InnerXml;
            }
        }
    }
}
